'use strict'

const expect = require('chai').expect
const sinon = require('sinon')
const Serverless = require('../../../../../lib/serverless')
const AwsProvider = require('../../../../../lib/plugins/aws/provider')
const CLI = require('../../../../../lib/classes/cli')
const proxyquire = require('proxyquire')

describe('AwsRollbackFunction', () => {
  let serverless
  let awsRollbackFunction
  let fetchStub
  let AwsRollbackFunction

  beforeEach(() => {
    fetchStub = sinon.stub().resolves({ buffer: () => {} })
    AwsRollbackFunction = proxyquire(
      '../../../../../lib/plugins/aws/rollback-function.js',
      {
        'node-fetch': fetchStub,
      },
    )
    serverless = new Serverless({ commands: [], options: {} })
    serverless.service.functions = {
      hello: {
        handler: true,
        name: 'service-dev-hello',
      },
    }
    const options = {
      stage: 'dev',
      region: 'us-east-1',
      function: 'hello',
    }
    serverless.setProvider('aws', new AwsProvider(serverless, options))
    serverless.cli = new CLI(serverless)
    awsRollbackFunction = new AwsRollbackFunction(serverless, options)
  })

  describe('#constructor()', () => {
    let validateStub
    let getFunctionToBeRestoredStub
    let fetchFunctionCodeStub
    let restoreFunctionStub

    beforeEach(() => {
      validateStub = sinon.stub(awsRollbackFunction, 'validate').resolves()
      getFunctionToBeRestoredStub = sinon
        .stub(awsRollbackFunction, 'getFunctionToBeRestored')
        .resolves()
      fetchFunctionCodeStub = sinon
        .stub(awsRollbackFunction, 'fetchFunctionCode')
        .resolves()
      restoreFunctionStub = sinon
        .stub(awsRollbackFunction, 'restoreFunction')
        .resolves()
    })

    afterEach(() => {
      awsRollbackFunction.validate.restore()
      awsRollbackFunction.getFunctionToBeRestored.restore()
      awsRollbackFunction.fetchFunctionCode.restore()
      awsRollbackFunction.restoreFunction.restore()
    })

    it('should have hooks', () =>
      expect(awsRollbackFunction.hooks).to.be.not.empty)

    it('should set the provider variable to an instance of AwsProvider', () =>
      expect(awsRollbackFunction.provider).to.be.instanceof(AwsProvider))

    it('should set an empty options object if no options are given', () => {
      const awsRollbackFunctionWithEmptyOptions = new AwsRollbackFunction(
        serverless,
      )
      expect(awsRollbackFunctionWithEmptyOptions.options).to.deep.equal({})
    })

    it('should run promise chain in order', async () =>
      awsRollbackFunction.hooks['rollback:function:rollback']().then(() => {
        expect(validateStub.calledOnce).to.equal(true)
        expect(getFunctionToBeRestoredStub.calledAfter(validateStub)).to.equal(
          true,
        )
        expect(
          fetchFunctionCodeStub.calledAfter(getFunctionToBeRestoredStub),
        ).to.equal(true)
        expect(restoreFunctionStub.calledAfter(fetchFunctionCodeStub)).to.equal(
          true,
        )
      }))
  })

  describe('#getFunctionToBeRestored()', () => {
    describe('when function and version can be found', () => {
      let getFunctionStub

      beforeEach(() => {
        getFunctionStub = sinon
          .stub(awsRollbackFunction.provider, 'request')
          .resolves({ function: 'hello' })
      })

      afterEach(() => {
        awsRollbackFunction.provider.request.restore()
      })

      it('should return the requested function', async () => {
        awsRollbackFunction.options.function = 'hello'
        awsRollbackFunction.options['function-version'] = '4711'

        return awsRollbackFunction.getFunctionToBeRestored().then((result) => {
          expect(getFunctionStub.calledOnce).to.equal(true)
          expect(
            getFunctionStub.calledWithExactly('Lambda', 'getFunction', {
              FunctionName: 'service-dev-hello',
              Qualifier: '4711',
            }),
          ).to.equal(true)
          expect(result).to.deep.equal({ function: 'hello' })
        })
      })
    })

    describe('when function or version could not be found', () => {
      let getFunctionStub

      beforeEach(() => {
        getFunctionStub = sinon
          .stub(awsRollbackFunction.provider, 'request')
          .rejects(new Error('function hello not found'))
      })

      afterEach(() => {
        awsRollbackFunction.provider.request.restore()
      })

      it('should translate the error message to a custom error message', () => {
        awsRollbackFunction.options.function = 'hello'
        awsRollbackFunction.options['function-version'] = '4711'

        return awsRollbackFunction.getFunctionToBeRestored().catch((error) => {
          expect(
            error.message.match(
              /Function "hello" with version "4711" not found/,
            ),
          )
          expect(getFunctionStub.calledOnce).to.equal(true)
          expect(
            getFunctionStub.calledWithExactly('Lambda', 'getFunction', {
              FunctionName: 'service-dev-hello',
              Qualifier: '4711',
            }),
          ).to.equal(true)
        })
      })
    })

    describe('when other error occurred', () => {
      let getFunctionStub

      beforeEach(() => {
        getFunctionStub = sinon
          .stub(awsRollbackFunction.provider, 'request')
          .rejects(new Error('something went wrong'))
      })

      afterEach(() => {
        awsRollbackFunction.provider.request.restore()
      })

      it('should re-throw the error without translating it to a custom error message', () => {
        awsRollbackFunction.options.function = 'hello'
        awsRollbackFunction.options['function-version'] = '4711'

        return awsRollbackFunction.getFunctionToBeRestored().catch((error) => {
          expect(error.message.match(/something went wrong/))
          expect(getFunctionStub.calledOnce).to.equal(true)
          expect(
            getFunctionStub.calledWithExactly('Lambda', 'getFunction', {
              FunctionName: 'service-dev-hello',
              Qualifier: '4711',
            }),
          ).to.equal(true)
        })
      })
    })
  })

  describe('#fetchFunctionCode()', () => {
    it('should fetch the zip file content of the previously requested function', async () => {
      const func = {
        Code: {
          Location: 'https://foo.com/bar',
        },
      }

      return awsRollbackFunction.fetchFunctionCode(func).then(() => {
        expect(fetchStub.called).to.equal(true)
      })
    })
  })

  describe('#restoreFunction()', () => {
    let updateFunctionCodeStub

    beforeEach(() => {
      updateFunctionCodeStub = sinon
        .stub(awsRollbackFunction.provider, 'request')
        .resolves()
    })

    afterEach(() => {
      awsRollbackFunction.provider.request.restore()
    })

    it('should restore the provided function', async () => {
      awsRollbackFunction.options.function = 'hello'
      const zipBuffer = Buffer.from('')

      return awsRollbackFunction.restoreFunction(zipBuffer).then(() => {
        expect(updateFunctionCodeStub.calledOnce).to.equal(true)
        expect(
          updateFunctionCodeStub.calledWithExactly(
            'Lambda',
            'updateFunctionCode',
            {
              FunctionName: 'service-dev-hello',
              ZipFile: zipBuffer,
            },
          ),
        ).to.equal(true)
      })
    })
  })
})
